package dice.sinanya.tools.makedata;

import com.forte.qqrobot.beans.messages.types.MsgGetTypes;
import dice.sinanya.db.npclist.InsertNpcList;
import dice.sinanya.dice.get.imal.GetRandomList;
import dice.sinanya.entity.EntityGroupSwitch;
import dice.sinanya.entity.EntityRoleTag;
import dice.sinanya.entity.EntityStrManyRolls;
import dice.sinanya.entity.EntityTypeMessages;
import dice.sinanya.exceptions.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import java.util.regex.Pattern;

import static dice.sinanya.system.MessagesBanList.groupSwitchHashMap;
import static dice.sinanya.system.MessagesSystem.*;
import static dice.sinanya.system.NpcListCache.NPC_LIST;
import static dice.sinanya.system.RoleInfoCache.ROLE_INFO_CACHE;
import static dice.sinanya.tools.checkdata.CheckIsNumbers.isNumeric;
import static dice.sinanya.tools.checkdata.CheckList.checkNpcName;
import static dice.sinanya.tools.getinfo.GetMessagesProperties.entitySystemProperties;
import static dice.sinanya.tools.getinfo.GetNickName.getNickName;
import static dice.sinanya.tools.getinfo.Kp.getKpGroupFromGroup;
import static dice.sinanya.tools.getinfo.RoleChoose.getRoleChooseByQQ;
import static dice.sinanya.tools.getinfo.RoleInfo.*;
import static dice.sinanya.tools.makedata.GetRollResultAndStr.getResFunctionAndResultInt;
import static dice.sinanya.tools.makedata.RandomInt.random;
import static dice.sinanya.tools.makedata.Sender.sender;
import static dice.sinanya.tools.makedata.StringFormatById.stringFormatById;
import static java.lang.Math.max;
import static java.lang.Math.min;

/**
 * @author SitaNya
 * 日期: 2019-06-15
 * 电子邮箱: sitanya@qq.com
 * 维护群(QQ): 162279609
 * 有任何问题欢迎咨询
 * 类说明: 进行SanCheck检定
 */
public class MakeSanCheck implements GetRandomList {

    private static final Logger log = LogManager.getLogger(MakeSanCheck.class.getName());
    private static final Pattern plus = Pattern.compile("[+*/\\-]");
    private final EntityTypeMessages entityTypeMessages;
    private final long qq;


    public MakeSanCheck(EntityTypeMessages entityTypeMessages) {
        qq = entityTypeMessages.getFromQq();
        this.entityTypeMessages = entityTypeMessages;
    }

    public MakeSanCheck(EntityTypeMessages entityTypeMessages, String qqId) {
        qq = Long.parseLong(qqId);
        this.entityTypeMessages = entityTypeMessages;
    }

    public MakeSanCheck(EntityTypeMessages entityTypeMessages, long qq) {
        this.qq = qq;
        this.entityTypeMessages = entityTypeMessages;
    }

    /**
     * 恢复san值方法，用于team命令
     *
     * @param function 恢复的字符串，可以是1D3|1D3+5等
     * @throws PlayerSetException 如果无法解析，则返回玩家输入值报错
     */
    public void addSanCheck(String function) throws PlayerSetException {
        String role = NONE;
        int san = 0;
        int cthulhuMythos = 0;
        String groupId = entityTypeMessages.getFromGroupString();

        int changeValue;
        String changeFunction;
        String strAddValue;


        if (function.contains(SPACE) && !function.split(SPACE)[1].isEmpty()) {
            strAddValue = function.split(SPACE)[0];
            if (isNumeric(function.split(SPACE)[1])) {
                san = Integer.parseInt(function.split(SPACE)[1]);
            }
        } else {
            strAddValue = function;
        }
//        如果字符串中含有空格且第二位不为空，则认为是指定了san值。否则整段都是表达式


        if (isNumeric(strAddValue)) {
            changeValue = Integer.parseInt(strAddValue);
            changeFunction = strAddValue;
        } else {
            GetRollResultAndStr getRollResultAndStr = null;
            try {
                getRollResultAndStr = new GetRollResultAndStr(entityTypeMessages, strAddValue);
            } catch (ManyRollsTimesTooMoreException | RollCantInZeroException e) {
                log.error(e.getMessage(), e);
            }
            assert getRollResultAndStr != null;
            changeValue = getRollResultAndStr.getResInt();
            changeFunction = getRollResultAndStr.getResStr();
        }
//        如果表达式是数字，那么直接恢复即可。如果不是则需要过一下GetRollResultAndStr方法计算最终的值

        HashMap<String, Integer> prop = (HashMap<String, Integer>) getRoleInfoFromChooseByQQ(qq, groupId);
        if (san == 0 && prop != null) {
            role = getRoleChooseByQQ(qq, groupId);
            san = prop.get("san");
            cthulhuMythos = prop.get("cthulhuMythos");
            prop.put("san", san + changeValue);
            setRoleInfoFromChooseByQQ(qq, groupId, prop);
//            计算新的值后更新人物卡
        } else if (san == 0) {
            throw new PlayerSetException(entityTypeMessages);
        }
//        如果san为0，也就是玩家没有输入san值的同时，人物卡还没取到的话就报错。否则优先用玩家输入的san，然后用人物卡。
        sender(entityTypeMessages, "已为[" + role + "]恢复" + function + "=" + changeValue + "点理智值，剩余" + min((san + changeValue), 99 - cthulhuMythos) + "点");
    }

    @SuppressWarnings("AlibabaMethodTooLong")
    public String checkSanCheck(String function, String qq) throws SanCheckSetException, PlayerSetException, ManyRollsTimesTooMoreException, NotSetKpGroupException {
        boolean useCustomRuleEnable = useCustomRule.contains(entityTypeMessages.getFromGroupString()) || entityTypeMessages.getMsgGetTypes() == MsgGetTypes.privateMsg;
        String groupId = entityTypeMessages.getFromGroupString();
        String sanFunctionSeq = "/";
        String sanText = "%s/%s=%s\n你的理智值减少%s=%s点,当前剩余%s点%s";

        String strCheckValue;
        String role = null;
        int san = 0;
        boolean useList = false;
        HashMap<String, Integer> prop = null;

        /*
         * 如果字符串中含有空格且第二位不为空，则认为是指定了san值。否则整段都是表达式
         */
        if (function.contains(SPACE) && !function.split(SPACE)[1].isEmpty()) {
            strCheckValue = function.split(SPACE)[0];
            if (isNumeric(function.split(SPACE)[1])) {
                san = Integer.parseInt(function.split(SPACE)[1]);
            } else if (checkRoleInfoExistByQQNotLock(Long.parseLong(qq), function.split(SPACE)[1])) {
                san = Objects.requireNonNull(getRoleInfoByQQNotLock(Long.parseLong(qq), entityTypeMessages.getFromGroupString(), function.split(SPACE)[1])).getOrDefault("san", 0);
                role = function.split(SPACE)[1];
            } else if (checkNpcName(groupId, function.split(SPACE)[1])) {
                role = function.split(SPACE)[1];
                san = NPC_LIST.get(groupId).get(role).getOrDefault("san", 0);
                prop = NPC_LIST.get(groupId).get(role);
                useList = true;
            }
        } else {
            strCheckValue = function;
        }


        String tagNone = NONE;
        boolean containsSeq = strCheckValue.contains(sanFunctionSeq) && strCheckValue.split(sanFunctionSeq).length == 2;
        boolean firstFunctionError = strCheckValue.split(sanFunctionSeq)[0].equals(tagNone);
        boolean secondFunctionError = containsSeq && strCheckValue.split(sanFunctionSeq)[1].equals(tagNone);
        boolean functionError = !containsSeq || firstFunctionError || secondFunctionError;
        if (functionError) {
            throw new SanCheckSetException(entityTypeMessages);
        }
//        确认表达式合规

        String strSuccess = strCheckValue.split(sanFunctionSeq)[0];
        String strFail = strCheckValue.split(sanFunctionSeq)[1];
//        分别取得成功与失败的表达式

        String[] everyFunctionSuccess = strSuccess.split(plus.toString());
        String[] everyFunctionFail = strFail.split(plus.toString());

        EntityStrManyRolls mSuccess;
        EntityStrManyRolls mFail;

        boolean useCard = false;

        try {
            mSuccess = getResFunctionAndResultInt(entityTypeMessages, strSuccess, everyFunctionSuccess);
            mFail = getResFunctionAndResultInt(entityTypeMessages, strFail, everyFunctionFail);
//        分别计算成功与失败的表达式

            if (!useList) {
                if (role != null) {
                    prop = (HashMap<String, Integer>) getRoleInfoByQQNotLock(Long.parseLong(qq), entityTypeMessages.getFromGroupString(), function.split(SPACE)[1]);
                    useCard = true;
                } else {
//        默认为未使用人物卡
                    prop = (HashMap<String, Integer>) getRoleInfoFromChooseByQQ(qq, groupId);
                    if (san == 0 && prop != null) {
                        role = getRoleChooseByQQ(qq, groupId);
                        san = prop.get("san");
                        useCard = true;
//            如果读到了人物卡，将使用人物卡标志位记为打开
                    } else if (san == 0) {
                        throw new PlayerSetException(entityTypeMessages);
                    } else {
                        role = getNickName(Long.parseLong(qq), entityTypeMessages);
                    }
                }
            }
//        如果san为0，也就是玩家没有输入san值的同时，人物卡还没取到的话就报错。否则优先用玩家输入的san，然后用人物卡。
        } catch (Exception e) {
            return "error";
        }

        StringBuilder strResult = new StringBuilder();
        strResult.append("[").append(role).append("]")
                .append("的理智检定结果:")
                .append("\n");
//        初始化回复字符串

        int random = random(1, 100, entityTypeMessages);
//        骰点
        String regexFunctionSeq = "[dD]";
        int newSan;

        boolean groupHasSwitch = groupSwitchHashMap.containsKey(entityTypeMessages.getFromGroup());

        int levelFumbleLine;
        int levelCriticalSuccess;

        if (san < 50) {
            levelCriticalSuccess = 1;
            levelFumbleLine = 100;
        } else {
            levelCriticalSuccess = 1;
            levelFumbleLine = 96;
        }

        if (useCustomRuleEnable) {
            if (groupSwitchHashMap.containsKey(entityTypeMessages.getFromGroup())) {
                levelFumbleLine = 100 - groupSwitchHashMap.get(entityTypeMessages.getFromGroup()).getRule();
                levelCriticalSuccess = groupSwitchHashMap.get(entityTypeMessages.getFromGroup()).getRule();
            } else {
                levelFumbleLine = 96;
                levelCriticalSuccess = 5;
            }
        }

        if (random >= levelFumbleLine) {
//            如果大失败，默认掉最大值
            int maxSan = hasFunctionSeq(strFail) ? getMaxSan(strFail.split(regexFunctionSeq)[1]) : getMaxSan(strFail);
            newSan = max(0, san - maxSan);
            if (!groupHasSwitch || !groupSwitchHashMap.get(entityTypeMessages.getFromGroup()).isSimple()) {
                strResult.append(stringFormatById(sanText, random, san, "大失败", strFail, maxSan, newSan, makeScResult(entitySystemProperties.getSanCheckFumble(entityTypeMessages.getFromGroupString()))));
            } else {
                strResult.append(stringFormatById(sanText, random, san, "大失败", strFail, maxSan, newSan, ""));
            }

            makeInsane(strResult, newSan, san);
        } else if (random <= levelCriticalSuccess) {
            newSan = max(0, san - (int) mSuccess.getResult());
            if (!groupHasSwitch || !groupSwitchHashMap.get(entityTypeMessages.getFromGroup()).isSimple()) {
                strResult.append(stringFormatById(sanText, random, san, "大成功", strSuccess, mSuccess.getResult(), newSan, makeScResult(entitySystemProperties.getSanCheckCriticalSuccess(entityTypeMessages.getFromGroupString())) + "\n本次理智检定大成功，调查员可考虑使用规则书第8章的自救规则，详情请听从守秘人指示"));
            } else {
                strResult.append(stringFormatById(sanText, random, san, "大成功", strSuccess, mSuccess.getResult(), newSan, "\n本次理智检定大成功，调查员可考虑使用规则书第8章的自救规则，详情请听从守秘人指示"));
            }
            makeInsane(strResult, newSan, san);
        } else if (random <= san) {
            newSan = max(0, san - (int) mSuccess.getResult());
            if (!groupHasSwitch || !groupSwitchHashMap.get(entityTypeMessages.getFromGroup()).isSimple()) {
                strResult.append(stringFormatById(sanText, random, san, "成功", strSuccess, mSuccess.getResult(), newSan, makeScResult(entitySystemProperties.getSanCheckSuccess(entityTypeMessages.getFromGroupString()))));
            } else {
                strResult.append(stringFormatById(sanText, random, san, "成功", strSuccess, mSuccess.getResult(), newSan, ""));
            }


            makeInsane(strResult, newSan, san);
        } else {
            newSan = max(0, san - (int) mFail.getResult());
            if (!groupHasSwitch || !groupSwitchHashMap.get(entityTypeMessages.getFromGroup()).isSimple()) {
                strResult.append(stringFormatById(sanText, random, san, "失败", strFail, mFail.getResult(), newSan, makeScResult(entitySystemProperties.getSanCheckFailure(entityTypeMessages.getFromGroupString()))));
            } else {
                strResult.append(stringFormatById(sanText, random, san, "失败", strFail, mFail.getResult(), newSan, ""));
            }
            makeInsane(strResult, newSan, san);
        }
        if (useCard) {
            assert prop != null;
            setCard(newSan, prop, role);
        }
        if (useList) {
            prop.put("san", newSan);
            new InsertNpcList().insertNpcList(groupId, role, prop);
        }
        if (groupSwitchHashMap.getOrDefault(entityTypeMessages.getFromGroup(), new EntityGroupSwitch()).isSecret()) {
            String kpId = getKpGroupFromGroup(entityTypeMessages);
            if (kpId == null) {
                sender(entityTypeMessages, "当前群开启了秘密团模式，但未找到kp，请团内kp在本群输入.kp");
                throw new NotSetKpGroupException(entityTypeMessages);
            }
            entityTypeMessages.getMsgSender().SENDER.sendPrivateMsg(kpId, strResult.toString());
            entityTypeMessages.getMsgSender().SENDER.sendPrivateMsg(entityTypeMessages.getFromQqString(), strResult.toString());
            return stringFormatById("进行理智鉴定，因秘密团隐藏骰点,结果已私聊给KP:[CQ:at,qq=%s]", kpId);
        } else {
            return strResult.toString();
        }
    }


    /**
     * 更新人物卡信息，不过这里必须注意，人工输入san值是不会更新任务卡信息
     *
     * @param newSan 新的san值
     * @param prop   包含所有属性值的HashMap列表
     * @param role   角色名
     */
    private void setCard(int newSan, HashMap<String, Integer> prop, String role) {
        prop.put("san", newSan);
        setRoleInfoByQQ(qq, role, prop);
        ROLE_INFO_CACHE.put(new EntityRoleTag(qq, role), prop);
    }

    /**
     * 根据san值变化判断是否疯狂
     *
     * @param strResult StringBuilder对象，用于整合回复字符串
     * @param newSan    新的san值
     * @param san       原本的san值
     */
    private void makeInsane(StringBuilder strResult, int newSan, int san) {
        if (newSan == 0) {
            strResult.append("\n已永久疯狂");
        } else if (san - newSan >= 5 && (san - newSan) < (san / 5)) {
            strResult.append("\n已进入临时性疯狂（请KP注意需要int检定成功后才进行疯狂，此信息只做提示）");
        } else if ((san - newSan) >= (san / 5)) {
            strResult.append("\n已因单次损失值进入不定性疯狂");
        } else {
            return;
        }
        boolean groupHasSwitch = groupSwitchHashMap.containsKey(entityTypeMessages.getFromGroup());
        if (!groupHasSwitch || !groupSwitchHashMap.get(entityTypeMessages.getFromGroup()).isSimple()) {
            strResult.append(entitySystemProperties.getSymptom(entityTypeMessages.getFromGroupString()));
        }
    }

    /**
     * 判断输入字符串是否为表达式，表达式一定带有d或D
     *
     * @param function 输入字符串
     * @return 是否正常
     */
    private boolean hasFunctionSeq(String function) {
        return function.contains("D") || function.contains("d");
    }

    private int getMaxSan(String failFunction) {
        int maxSan = 0;
        if (isNumeric(failFunction)) {
            maxSan = Integer.parseInt(failFunction);
        } else {
            sender(entityTypeMessages, entitySystemProperties.getSanCheck(entityTypeMessages.getFromGroupString()));
        }
        return maxSan;
    }

    private int getMinSan(String failFunction) {
        int minSan = 0;
        if (isNumeric(failFunction)) {
            minSan = Integer.parseInt(failFunction);
        } else {
            sender(entityTypeMessages, entitySystemProperties.getSanCheck(entityTypeMessages.getFromGroupString()));
        }
        return minSan;
    }

    private String makeScResult(String text) {
        if (text.contains("|") && text.split("\\|").length > 1) {
            String result = randomFromList(new ArrayList<>(Arrays.asList(text.split("\\|"))));
            if (!result.startsWith("\n")) {
                return "\n" + result;
            } else {
                return result;
            }
        } else {
            return text;
        }
    }
}
